// SPDX-License-Identifier: GPL-3.0-or-later
// Copyright © 2019 Ariadne Devos

#include <limits.h>
#include <stddef.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>

#include <sHT/string.h>
#include <sHT/test.h>

/* Test arithmetic and logical operators.
  No mutation or loops are present.  */

/* Some positive, negative,
  some even, some odd (x86),
  a bit here and there,
  and the same about their differences
  (cmp: A <- B - C) */
static const size_t interesting[] = {
	0,
	1,
	2,
	3,

	SIZE_MAX/3 - 2,
	SIZE_MAX/3 - 1,
	SIZE_MAX/3,
	SIZE_MAX/3 + 1,
	SIZE_MAX/3 + 2,

	SIZE_MAX/2 - 2,
	SIZE_MAX/2 - 1,
	SIZE_MAX/2,
	SIZE_MAX/2 + 1,
	SIZE_MAX/2 + 2,

	2 * (SIZE_MAX/3) - 2,
	2 * (SIZE_MAX/3) - 1,
	2 * (SIZE_MAX/3),
	2 * (SIZE_MAX/3) + 1,
	2 * (SIZE_MAX/3) + 2,

	SIZE_MAX - 3,
	SIZE_MAX - 2,
	SIZE_MAX - 1,
	SIZE_MAX - 0,
};

#define N (sizeof(interesting)/sizeof(size_t))

static int ret = 0;

static void
fail(const char *fun, size_t a, size_t b)
{
	/* a and b are unused, but GCC doesn't optimise
	  them away -- a good thing for debugging */
	if (printf("FAIL: test/%s\n", fun, a, b) < 0)
		exit(2);
	ret = 1;
}

static void
pass(const char *fun)
{
	if (printf("PASS: test/%s\n", fun) < 0)
		exit(2);
}


/* test different immediate modes (x86, x86-64, in this case) */
#define beef0 0xde
#define beef1 0xdead
#if UINT32_MAX && (SIZE_MAX >= UINT32_MAX)
# define beef2 0xdeadbe
# define beef3 0xdeadbeef
#else
# define beef2 beef0
# define beef3 beef1
#endif
#if UINT64_MAX && (SIZE_MAX >= UINT64_MAX)
# define beef4 0xdeadbeefdeafca
# define beef5 0xdeadbeefdeafcafe
#else
# define beef4 beef2
# define beef5 beef3
#endif

/* Don't do sHT_index_iterate here, as that would be
  a rather cyclic test. */
#define test_op(fun, check) \
	for (unsigned i = 0; i < N; i++) { \
		for (unsigned j = 0; j < N; j++) { \
			size_t a = interesting[i]; \
			size_t b = interesting[j]; \
			/* register / register */ \
			if (!(check)) { \
				fail(#fun, a, b); \
				goto nexttest_##fun; \
			} \
		} \
		/* register / immediate (32 / 64) */ \
		size_t a = interesting[i]; \
		size_t b = beef0; \
		if (!(check)) \
			fail(#fun, a, b); \
		b = beef1; \
		if (!(check)) \
			fail(#fun, a, b); \
		b = beef2; \
		if (!(check)) \
			fail(#fun, a, b); \
		b = beef3; \
		if (!(check)) \
			fail(#fun, a, b); \
		b = beef4; \
		if (!(check)) \
			fail(#fun, a, b); \
		b = beef5; \
		if (!(check)) \
			fail(#fun, a, b); \
	} \
	pass(#fun); \
nexttest_##fun: \

#define test_op1(fun, op) test_op(fun, sHT_##fun(a) == (a op))
#define test_op1_bool(fun, op) test_op(fun, !!sHT_##fun(a) == !!((a) op))
#define test_op1_signedbool(fun, op) test_op(fun, !!sHT_##fun((ssize_t) a) == !!(((ssize_t) a) op))
#define test_op2(fun, op) test_op(fun, sHT_##fun(a, b) == (a op b))
#define test_op2_bool(fun, op) test_op(fun, !!sHT_##fun(a, b) == !!(a op b))
#define test_op2_negatebool(fun, op) test_op(fun, !!sHT_##fun(a, b) == !!!(a op b))
#define test_fun2(fun) test_op(fun, sHT_##fun(a, b) == ref_##fun(a, b))

/* TODO: test performance (static branch prediction) */

static size_t
ref_min_size(size_t a, size_t b)
{
	if (a > b)
		return b;
	return a;
}

int
main(void)
{
	test_op2_bool(gt, >);
	test_op2_bool(ge, >=);
	test_op2_bool(eq, ==);
	test_op2_bool(eq_bool, ==);
	test_op2_bool(neq, !=);

	test_op1_signedbool(lt0, < 0);
	test_op1_bool(zero_p, == 0);
	test_op1_bool(nonzero_p, != 0);

	test_op2_bool(and_any, &);
	test_op2_negatebool(and_none, &);

	test_fun2(min_size);

	return ret;
}
